let s:thisplugin = expand('<sfile>:p:h:h')
let s:qargpattern = '\v\s*(\S+)%(\s+(.*))?$'
let s:cmdlinepattern = '\v\CG%[laive][!]?\s*(\S*)\s*(.*)$'


""
" Installs this plugin. The maktaba library must be available: either make sure
" it's on your runtimepath or put it in the same directory as glaive and source
" the glaive bootstrap file. (If you source the bootstrap file, there is no need
" to call this function.)
function! glaive#Install() abort
  let l:glaive = maktaba#plugin#GetOrInstall(s:thisplugin)
  call l:glaive.Load('commands')
endfunction


" Returns completion candidates for the partial plugin name {string}.
function! s:CompletePluginName(string) abort
  let l:plugins = maktaba#plugin#RegisteredPlugins()
  let l:canonical_name = maktaba#plugin#CanonicalName(a:string)
  call filter(l:plugins, 'maktaba#string#StartsWith(v:val, l:canonical_name)')
  return l:plugins
endfunction


" Adds the current value to the end of the flag assignment {string}.
function! s:CompleteCurrentFlagValue(plugin, string) abort
  let l:flagname = substitute(a:string, '\v[-+^$`]?\=$', '', '')
  try
    " l:Flagvalue must be capitalized because it may receive a Funcref.
    let l:Flagvalue = a:plugin.Flag(l:flagname)
  catch /ERROR(\(NotFound\|BadValue\)):/
    return []
  endtry
  if maktaba#value#IsString(l:Flagvalue) || maktaba#value#IsNumeric(l:Flagvalue)
    let l:valstring = string(l:Flagvalue)
  else
    let l:valstring = '`' . string(l:Flagvalue) . '`'
  endif
  return [a:string . l:valstring]
endfunction


" Returns completion candidates for the partial flag handle {string}.
function! s:CompleteFlagHandle(plugin, string) abort
  let l:unaryop = matchstr(a:string, '\v^[!~]?')
  let l:handle = maktaba#string#StripLeading(a:string, '!~')

  " No sophisticated decision making here. We first assume that the string is a
  " handle with foci and attempt to parse it. If this fails, we assume the flag
  " to be "plain" (simple flag, no foci) and take the simpler route below.
  let l:isplain = 0
  try
    let [l:flagname, l:foci, l:rest] = maktaba#setting#ParseHandle(l:handle)
  catch /ERROR(BadValue):/
    " This exception is thrown for an empty handle, too. Try plain completion.
    let l:isplain = 1
  endtry

  if l:isplain || empty(l:foci) && empty(l:rest)
    " Complete just the flag name.
    let l:flags = keys(a:plugin.flags)
    call filter(l:flags, 'maktaba#string#StartsWith(v:val, l:handle)')
    return map(l:flags, 'l:unaryop . v:val')
  elseif maktaba#string#StartsWith(l:rest, '[')
    try
      let l:flag = a:plugin.Flag(l:flagname)
      let l:focus = maktaba#value#Focus(l:flag, l:foci)
    catch /ERROR(\(BadValue\|NotFound\)):/
      return []
    endtry
    " Do completion for dictionary and list type foci.
    if maktaba#value#IsDict(l:focus)
      let l:candidates = keys(l:focus)
    elseif maktaba#value#IsList(l:focus)
      let l:candidates = map(range(len(l:focus)), 'string(v:val)')
    else
      return []
    endif
    let l:handleprefix = l:flagname . join(map(l:foci, '"[".v:val."]"'), '')
    let l:current = maktaba#string#StripLeading(l:rest, '[')
    call filter(l:candidates, 'maktaba#string#StartsWith(v:val, l:current)')
    return map(l:candidates, 'l:unaryop . l:handleprefix . "[" . v:val')
  endif
  return []
endfunction


" Returns completion candidates for partial operation {string} for {plugin}.
function! s:CompleteOperation(plugin, string) abort
  if maktaba#string#EndsWith(a:string, '=')
    return s:CompleteCurrentFlagValue(a:plugin, a:string)
  else
    return s:CompleteFlagHandle(a:plugin, a:string)
  endif
endfunction


""
" Custom completion for the :Glaive command. Completion is performed for the
" plugin name, the flag handle, and the current flag value after '='. The names
" and meaning of {ArgLead}, {CmdLine}, and {CursorPos} are conventional, see
" |:command-completion-custom|.
function! glaive#Complete(ArgLead, CmdLine, CursorPos) abort
  " This pattern must be somewhat permissive because ": ::4,'XGlaiv! ..." is
  " entirely within possibility as the beginning of {CmdLine}.
  let l:nameendpos = matchend(a:CmdLine, '\v\CG%[laive][!]?\s+\S*')
  if a:CursorPos <= l:nameendpos
    return s:CompletePluginName(a:ArgLead)
  else
    " s:cmdlinepattern must always match a:CmdLine.
    let [l:name, l:operations] = matchlist(a:CmdLine, s:cmdlinepattern)[1:2]
    try
      let l:plugin = maktaba#plugin#Get(l:name)
    catch /ERROR(NotFound):/
      return []
    endtry
    return s:CompleteOperation(l:plugin, a:ArgLead)
  endif
endfunction


""
" Given {qargs} (a quoted string given to the @command(Glaive) command, as
" generated by |<q-args>|), returns the plugin name and the configuration
" string.
" @throws BadValue if {qargs} has no plugin name.
function! glaive#SplitPluginNameFromOperations(qargs) abort
  let l:match = matchlist(a:qargs, s:qargpattern)
  if empty(l:match)
    throw maktaba#error#BadValue('Plugin missing in "%s"', a:qargs)
  endif
  return [l:match[1], l:match[2]]
endfunction


""
" Applies Glaive operations for {plugin} as described in {operations}.
" See @command(Glaive).
" @throws BadValue when the parsing of {operations} goes south.
" @throws WrongType when invalid flag operations are requested.
" @throws NotFound when a {operations} references a non-existent flag.
function! glaive#Configure(plugin, text) abort
  try
    let l:settings = maktaba#setting#ParseAll(maktaba#string#Strip(a:text))
  catch /ERROR(BadValue):/
    let [l:type, l:msg] = maktaba#error#Split(v:exception)
    let l:qualifier = 'Error parsing Glaive settings for %s: %s'
    throw maktaba#error#Message(l:type, l:qualifier, a:plugin.name, l:msg)
  endtry
  for l:setting in l:settings
    call l:setting.Apply(a:plugin)
  endfor
endfunction

""
" Gets {plugin}, which must already be on the runtimepath.
" Calls maktaba#plugin#Install on {plugin} if it has not yet been installed by
" maktaba. This will have no effect on non-maktaba plugins (which were already
" on the runtimepath), but will cause maktaba instant/* files to load (thus
" making their flags available).
"
" {plugin} will be passed through @function(maktaba#plugin#CanonicalName).
" Therefore, you can use anything which evaluates to the same canonical name:
" "my_plugin", "my-plugin", and even "my!plugin" are all equivalent.
"
" @throws NotFound if {plugin} cannot be found.
function! glaive#GetPlugin(plugin) abort
  " First, check whether the plugin was already registered with maktaba.
  try
    return maktaba#plugin#Get(a:plugin)
  catch /ERROR(NotFound):/
  endtry

  " Get the maktaba plugin object for a plugin already on the runtimepath.
  " If the plugin was installed with a plugin manager like pathogen or vundle,
  " then it's possible that it's on the runtimepath but hasn't been "Installed"
  " by maktaba. maktaba#plugin#Install is what forces the flags file to load
  " during vimrc time, so we need to make sure that the plugin has been
  " maktaba#plugin#Install'd before we can configure it.
  let l:canonical_name = maktaba#plugin#CanonicalName(a:plugin)
  let l:plugins = maktaba#rtp#LeafDirs()
  for l:key in keys(l:plugins)
    if maktaba#plugin#CanonicalName(l:key) ==# l:canonical_name
      return maktaba#plugin#GetOrInstall(l:plugins[l:key])
    endif
  endfor

  " If we're still here, we can't find the plugin.
  throw maktaba#error#NotFound('Plugin %s', a:plugin)
endfunction

function! glaive#PrintCurrentConfiguration(plugin) abort
  echomsg 'Flags for ' . a:plugin.name . ':'
  let l:flag_names = sort(keys(a:plugin.flags))
  for l:name in l:flag_names
    echomsg l:name . ': ' . string(a:plugin.flags[l:name].Get())
  endfor
endfunction
